﻿using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace VirtualKeyboard.Service.TcpServices
{
    public class ServiceListener
    {
        private readonly ITcpContextHandler contextHandler;
        private readonly Socket listener;
        private readonly SocketAsyncEventArgsPool eventPool;
        private readonly BufferManager bufferManager;
        private readonly Semaphore acceptSemaphore;

        private const int numConnections = 128;
        private const int bufferSize = 4 * 1024;

        public bool IsListen
        {
            get;
            private set;
        }

        public ServiceListener(ITcpContextHandler contextHandler)
        {
            this.contextHandler = contextHandler;
            this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.eventPool = new SocketAsyncEventArgsPool(numConnections);
            this.bufferManager = new BufferManager(bufferSize, numConnections);
            this.acceptSemaphore = new Semaphore(numConnections, numConnections);
        }

        public void Init()
        {
            for (int i = 0; i < numConnections; i++)
            {
                SocketAsyncEventArgs readWriteEventArg = new SocketAsyncEventArgs();
                readWriteEventArg.Completed += this.OnCompleted;
                readWriteEventArg.UserToken = new AsyncUserToken();
                this.bufferManager.SetBuffer(readWriteEventArg);
                this.eventPool.Push(readWriteEventArg);
            }

        }

        public void Start(int port)
        {
            if (this.IsListen)
            {
                return;
            }
            this.listener.Bind(new IPEndPoint(IPAddress.Any, port));
            this.listener.Listen(100);
            this.IsListen = true;
            var e = new SocketAsyncEventArgs();
            e.Completed += this.OnAccept;
            this.ProcessAccept(e);
        }

        public void Stop()
        {
            if (!this.IsListen)
            {
                return;
            }
            this.IsListen = false;
            this.listener.Close();
        }

        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            if (!IsListen)
            {
                return;
            }
            this.acceptSemaphore.WaitOne();
            bool willRaiseEvent = this.listener.AcceptAsync(e);
            if (!willRaiseEvent)
            {
                e.AcceptSocket = null;
                this.OnAccept(null, e);
            }
        }

        private void OnAccept(object sender, SocketAsyncEventArgs e)
        {

            try
            {
                if (e.SocketError != SocketError.Success)
                {
                    this.acceptSemaphore.Release();
                    return;
                }
                Socket client = e.AcceptSocket;
                if (client == null)
                {
                    this.acceptSemaphore.Release();
                    return;
                }
                var asyncEventArg = eventPool.Pop();
                if (asyncEventArg == null)
                {
                    this.acceptSemaphore.Release();
                    return;
                }
                var token = (AsyncUserToken)asyncEventArg.UserToken;
                token.Socket = client;
                token.EndPoint = (IPEndPoint)client.RemoteEndPoint;
                bool willRaiseEvent = client.ReceiveAsync(asyncEventArg);
                if (!willRaiseEvent)
                {
                    this.ProcessReceive(asyncEventArg);
                }
            }
            catch (Exception err)
            {
                Debug.Print("Accept exception,{0}", err);
            }
            finally
            {
                e.AcceptSocket = null;
                this.ProcessAccept(e);
            }
        }

        private void OnCompleted(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    this.ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Disconnect:
                    this.CloseClientSocket(e);
                    break;
                case SocketAsyncOperation.Send:
                    this.ProcessSend(e);
                    break;
            }
        }

        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            var token = (AsyncUserToken)e.UserToken;
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                try
                {
                    token.Analyse(e.Buffer, e.Offset, e.BytesTransferred);
                    contextHandler.ProcessRequest(token.Context);
                }
                catch (Exception err)
                {
                    Debug.Print("Accept exception,{0}", err);
                }

                if (token.Context.Response.Context != null)
                {
                    AnalyseHelper.SetBuffer(e, token.Context.Response);
                    bool willRaiseEvent = token.Socket.SendAsync(e);
                    if (!willRaiseEvent)
                    {
                        this.ProcessSend(e);
                    }
                }
                else
                {
                    this.CloseClientSocket(e);
                }
            }
            else
            {
                this.CloseClientSocket(e);
            }

        }

        private void ProcessSend(SocketAsyncEventArgs e)
        {
            e.SetBuffer(e.Offset, bufferSize);
            CloseClientSocket(e);
        }

        private void CloseClientSocket(SocketAsyncEventArgs e)
        {
            try
            {
                var token = e.UserToken as AsyncUserToken;
                if (token == null) return;

                token.Socket.Shutdown(SocketShutdown.Both);
                token.Socket.Close();
                token.Clear();
            }
            catch (Exception err)
            {
                Debug.Print("Accept exception,{0}", err);
            }
            finally
            {
                this.eventPool.Push(e);
                this.acceptSemaphore.Release();
            }
        }

    }
}
